package com.cy.pj.sys.service.aspect;

import com.cy.common.annotation.RequiredLog;
import com.cy.pj.sys.pojo.SysLog;
import com.cy.pj.sys.service.SysLogService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Date;

/**
 * spring AOP中 基于@Aspect注解描述的类型为切面类
 * 封装切入点和通知方法的定义
 * 切入扩展业务逻辑的一些目标方法的集合(使用特定注解描述的方法)
 * 通知:封装了扩展业务的一个方法(Spring使用特定注解描述)
 * @PointCut(切入点) 定义切入点 切入点表达式
 * 表示该注解描述的方法为一个切入点方法;在该方法上添加扩展业务逻辑
 * @Around(通知)
 *
 */
//@Slf4j
@Aspect
@Component
public class SysLogAspect {
    @Autowired
    private SysLogService sysLogService;

    //创建日志
    private static final Logger log = LoggerFactory.getLogger(SysLogAspect.class);

    //定义切入点,该方法为承载切入点的一个对象
    @Pointcut("@annotation(com.cy.common.annotation.RequiredLog)")
    public void doLog() {}//承载切入点的定义,换句话说 用方法为切入点起了个别名.

    //定义一个通知方法@Around 在此方法内部可以调用目标方法,可以在目标方法执行之前或之后
//    做一些拓展业务
    //参数jp 连接点 封装要执行的目标方法信息
    //返回值为 目标方法的执行结果 根据自定义的注解@RequiredLog 确定返回值类型 在ServiceImpl中
    //参数类型 ProceedingJoinPoint 此类型的参数只能定义在@Around描述的方法中

    @Around("doLog()")
    public Object doAround(ProceedingJoinPoint jp) throws Throwable {
        long t1 =System.currentTimeMillis();
        log.info("method start {}",t1);
        try {
            Object result = jp.proceed(); // 通过连接点(method.invok)调用目标方法(即LogServiceImpl方法)

            //System.out.println("123"+jp.proceed());
            long t2 = System.currentTimeMillis();
            log.info("method after {}",t2);

            doInsertLogInfo(jp,(t2-t1),null);

            return result;
        }catch (Throwable e){
            log.error("method error {}", System.currentTimeMillis());
            long t3 = System.currentTimeMillis();
            doInsertLogInfo(jp,(t3-t1),e);
            throw e;
        }
    }


    private void doInsertLogInfo(ProceedingJoinPoint jp,long time,Throwable e) throws NoSuchMethodException, JsonProcessingException {

        //一,获取用户行为日志
        String username ="admin";
        String ip = "192.168.1.1";
        //获取用户操作
        //3,获取RequiredLog注解
        //2,获取注解描述的方法对象 (字节码对象.方法名,参数列表)
        //1,获取方法类所在的字节码对象(目标对象对应的字节码对象)

        //获取注解描述的 字节码对象,方法名,参数列表
        //1,获取方法所在类的字节码对象(目标对象对应的类字节码对象)
        Class<?> clazz =jp.getTarget().getClass();
        //2,获取注解描述的方法对象(字节码对象,方法名,参数列表)
        //signature 方法签名 包含方法名和参数列表
        Signature signature = jp.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;

        //当代理为CGLIB时注解标记的方法
//        Method targetMethod = methodSignature.getMethod();
        //代理不用设置为CGLIB或者JDK
        //获取目标方法对象
        Method targetMethod =
        clazz.getDeclaredMethod(methodSignature.getName(),methodSignature.getParameterTypes());
        System.out.println("targetMethod"+targetMethod);

        //3 获取RequiredLog注解
        RequiredLog requiredlog = targetMethod.getAnnotation(RequiredLog.class);


        String operation =requiredlog.operation();
        //4获取方法声明
        String method = clazz.getName()+"."+targetMethod.getName();
        //5获取方法执行时传入的实际参数
        Object[] args = jp.getArgs(); //取到的数据为json格式[]
        //把json格式转为String格式  new ObjectMapper().writeValueAsString(args);
        String params = new ObjectMapper().writeValueAsString(args);
        //获取状态信息 int
        Integer status =e==null?1:0;
        //获取错误信息
        String error =e==null?"":e.getMessage();

        //二,封装用户行为日志
        SysLog sysLog =new SysLog();
        sysLog.setUsername(username);
        sysLog.setIp(ip);
        sysLog.setCreatedTime(new Date());
        sysLog.setOperation(operation);
        sysLog.setMethod(method);
        sysLog.setParams(params);
        sysLog.setStatus(status);
        sysLog.setError(error);
        sysLog.setTime(time);
        //将日志写入数据库通过LogService
        log.info("user log{}",new ObjectMapper().writeValueAsString(sysLog));
        //sysLogService.insertLog(sysLog);

        /**
         * 两个线程并发运行->异步运行
         * 解决因为线程问题而影响方法的查询响应时间
         *   分离查询与新增的同时操作,解决查询耗时问题,但是查询会存在阻塞
         *   但是创建线程会占用大量资源不适合大量访问,因此提供线程池思想
         */
        new Thread(){
            @Override
            public void run() {
                sysLogService.insertLog(sysLog);
            }
        }.start();

    }

}
